﻿using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using SynergyLib.SMath;


namespace SynergyControls
{
    
    public class RadialControl : Control, INotifyPropertyChanged
    {
        private enum RotationState
        {
            AnimatingToSnapAngle,
            AnimatingToPointer,
            SnappedToSnapAngle,
            SnappedToPointer,
            MouseUpAnimatingToSnapAngle,
        }

        enum RotationDirection
        { 
            Null,
            Clockwise,
            CounterClockwise,
        }

        #region Constants

        private const double offlimits = 0.001f;

        #endregion

        #region Fields
        StringCollection SnapValues = new StringCollection();
        RotationState State = RotationState.SnappedToSnapAngle;        
        public FrameworkElement Dial = null;
        public RadialGradientBrush rgb = null;
        public FrameworkElement _foreground = null;
        private double SnapTheta = 0d;
        private double DesiredTheta = 0d;
        private double PreviousThetaDelta = 0d;
        private double PointerSnapTheta = 0d;
        private Stopwatch stopwatch = new Stopwatch();
        private double AngularVelocity;
        private TimeSpan PrevTime;
        #endregion

        #region Properties
        private bool isLoaded { get; set; }
        private bool isAnimating { get; set; }
        /// <summary>
        /// Gets the resource root. This allow us to access the Storyboards in a Silverlight/WPf
        /// neutral manner
        /// </summary>
        /// <value>The resource root.</value>
        protected FrameworkElement ResourceRoot
        {
            get { return GetTemplateChild("LayoutRoot") as FrameworkElement; }
        }      
        private SolidColorBrush GradientLightBrush { get; set; }
        private SolidColorBrush GradientDarkBrush { get; set; }
        private RadialGradientBrush RadialGradientBrush
        {
            get
            {
                return rgb;
            }
            set
            {
                rgb = value;
            }
        }             
        public StringCollection SnapValuesProperty
        {
            get 
            { 
                return SnapValues; }
            set 
            { 
                SnapValues = value; 
            }
        }
        private RotationDirection Direction
        {
            get;
            
            set;
        }
        #endregion Properties

        #region Dependency Properties

        #region Colors


        public static readonly DependencyProperty GradientLightColorProperty =
            DependencyProperty.Register("GradientLightColor", typeof(Color), typeof(RadialControl), new PropertyMetadata(new PropertyChangedCallback(ColorPropertyChanged)));

        public static readonly DependencyProperty GradientDarkColorProperty =
            DependencyProperty.Register("GradientDarkColor", typeof(Color), typeof(RadialControl), new PropertyMetadata(new PropertyChangedCallback(ColorPropertyChanged)));


        private static void ColorPropertyChanged(DependencyObject dependency, DependencyPropertyChangedEventArgs args)
        {
            RadialControl instance = dependency as RadialControl;
            instance.GradientColorChanged();

            if (instance != null && instance.isLoaded)
            {
                if (instance.GradientLightColor != null && instance.GradientDarkColor != null)
                instance.SetDialGradient();
            }
        }

        public Color GradientLightColor
        {
            get 
            { 
                return (Color)GetValue(GradientLightColorProperty); 
            }
            set
            {
                SetValue(GradientLightColorProperty, value);
            }
        }

        public Color GradientDarkColor
        {
            get
            {
                return (Color)GetValue(GradientDarkColorProperty);
            }
            set
            {
                SetValue(GradientDarkColorProperty, value);
            }
        }        
      
        #endregion

        #region Angle

        /// <summary>
        /// The Angle relative to the 12 o'clock position.
        /// </summary>
        public static readonly DependencyProperty AngleProperty = DependencyProperty.Register("Angle", typeof(double), typeof(RadialControl), new FrameworkPropertyMetadata(0d, new PropertyChangedCallback(ThetaChanged), new CoerceValueCallback(CoerceTheta)));

        [Category("Common")]
        public double Angle
        {
            get 
            { 
                return (double)GetValue(AngleProperty); 
            }
            set 
            { 
                SetValue(AngleProperty, value);
                NotifyPropertyChanged("Angle");
            }
        }

        private static object CoerceTheta(DependencyObject o, object value)
        {
            RadialControl instance = o as RadialControl;
            if (instance != null)
            {
                double newTheta = (double)value;
				instance.SnapValue = instance.SnapValueFromSnapAngle(newTheta);
				return newTheta;
            }
            else return null;
        }
        private static void ThetaChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            RadialControl instance = o as RadialControl;
            if (instance != null)
            {
				if (instance.Dial != null)
					instance.Dial.RenderTransform = new RotateTransform((double)e.NewValue, 0.0d, 0.0d);
            }
        }

        #endregion

        #region ClockwiseMostAngle

        public static readonly DependencyProperty ClockwiseMostAngleProperty = DependencyProperty.Register("ClockwiseMostAngle", typeof(double), typeof(RadialControl), new FrameworkPropertyMetadata(160d));
        
        [Category("Common")]
        public double ClockwiseMostAngle
        {
            get 
            { 
                return (double)GetValue(ClockwiseMostAngleProperty); 
            }
            set
            { 
                SetValue(ClockwiseMostAngleProperty, value); 
            }
        }

        #endregion

        #region CounterclockwiseMostAngle

        public static readonly DependencyProperty CounterClockwiseMostAngleProperty = DependencyProperty.Register("CounterClockwiseMostAngle", typeof(double), typeof(RadialControl), new FrameworkPropertyMetadata(-160d));
       
        [Category("Common")]
        public double CounterClockwiseMostAngle
        {
            get 
            { 
                return (double)GetValue(CounterClockwiseMostAngleProperty); 
            }
            set 
            { 
                SetValue(CounterClockwiseMostAngleProperty, value);
            }
        }

        #endregion

        #region CursorAttractsNearestSnap


       /// <summary>
        /// Determines whether the mouse cursor should attract the point on the dial corresponding to the nearest snap angle.
       /// </summary>
        public static readonly DependencyProperty CursorAttractsNearestSnapProperty = DependencyProperty.Register("CursorAttractsNearestSnap", typeof(bool), typeof(RadialControl), new FrameworkPropertyMetadata(false));
       
        public bool CursorAttractsNearestSnap
        {
            get
            { 
                return (bool)GetValue(CursorAttractsNearestSnapProperty);
            }
            set 
            { 
                SetValue(CursorAttractsNearestSnapProperty, value); 
            }
        }

        #endregion

        #region RotationIsAnimated

        /// <summary>
        /// Whether or not dial rotation is animated.
        /// </summary>
        public static readonly DependencyProperty RotationIsAnimatedProperty = DependencyProperty.Register("RotationIsAnimated", typeof(bool), typeof(RadialControl), new FrameworkPropertyMetadata(true));

        [Category("Common")]
        public bool RotationIsAnimated
        {
            get 
            { 
                return (bool)GetValue(RotationIsAnimatedProperty);
            }
            set
            { 
                SetValue(RotationIsAnimatedProperty, value);
            }
        }

        #endregion

        #region RotationIsConstrained

        public static readonly DependencyProperty RotationIsConstrainedProperty = DependencyProperty.Register("RotationIsConstrained", typeof(bool), typeof(RadialControl), new FrameworkPropertyMetadata(false));
        
        [Category("Common")]
        public bool RotationIsConstrained
        {
            get 
            { 
                return (bool)GetValue(RotationIsConstrainedProperty); 
            }
            set 
            { 
                SetValue(RotationIsConstrainedProperty, value);
            }
        }

        #endregion

        #region SnapAngle

        public static readonly DependencyProperty SnapAngleProperty = DependencyProperty.Register("SnapAngle", typeof(double), typeof(RadialControl), new FrameworkPropertyMetadata(30d));
        
        [Category("Common")]
        public double SnapAngle
        {
            get 
            { 
                return (double)GetValue(SnapAngleProperty); 
            }
            set
            { 
                SetValue(SnapAngleProperty, value); 
            }
        }

        #endregion

        #region SnapSpringDampening

        public static readonly DependencyProperty SnapSpringDampeningProperty = DependencyProperty.Register("SnapSpringDampening", typeof(double), typeof(RadialControl), new FrameworkPropertyMetadata(17d));

        [Category("Common")]
        public double SnapSpringDampening
        {
            get 
            { 
                return (double)GetValue(SnapSpringDampeningProperty); 
            }
            set
            { 
                SetValue(SnapSpringDampeningProperty, value); 
            }
        }

        #endregion

        #region SnapSpringStrength

        public static readonly DependencyProperty SnapSpringStrengthProperty = DependencyProperty.Register("SnapSpringStrength", typeof(double), typeof(RadialControl), new FrameworkPropertyMetadata(1000d));
        
        [Category("Common")]
        public double SnapSpringStrength
        {
            get
            {
                return (double)GetValue(SnapSpringStrengthProperty); 
            }
            set 
            { 
                SetValue(SnapSpringStrengthProperty, value);
            }
        }

        #endregion

        #region SnapToSnapAngle

        public static readonly DependencyProperty SnapToSnapAngleProperty = DependencyProperty.Register("SnapToSnapAngle", typeof(bool), typeof(RadialControl), new FrameworkPropertyMetadata(true));

        [Category("Common")]
        public bool SnapToSnapAngle
        {
            get 
            { 
                return (bool)GetValue(SnapToSnapAngleProperty); 
            }
            set
            { 
                SetValue(SnapToSnapAngleProperty, value);
            }
        }

        #endregion

        #region SnapToValueTolerance

        public static readonly DependencyProperty SnapToValueToleranceProperty = DependencyProperty.Register("SnapToValueTolerance", typeof(double), typeof(RadialControl), new FrameworkPropertyMetadata(2d));
        
        [Category("Common")]
        public double SnapToValueTolerance
        {
            get
            { 
                return (double)GetValue(SnapToValueToleranceProperty);
            }
            set 
            { 
                SetValue(SnapToValueToleranceProperty, value);
            }
        }

        #endregion

        #region SnapValue

        public static readonly DependencyProperty SnapValueProperty = DependencyProperty.Register("SnapValue", typeof(string), typeof(RadialControl), new FrameworkPropertyMetadata(string.Empty));
		
        [Category("Common")]
        public string SnapValue
		{
			get 
            {
                return (string)GetValue(SnapValueProperty); 
            }
			set 
            { 
                SetValue(SnapValueProperty, value); 
            }
		}

        #endregion

        #endregion

        #region Constructors

        static RadialControl()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(RadialControl), new FrameworkPropertyMetadata(typeof(RadialControl)));
        }
        public RadialControl()
        { }

        #endregion

        #region Event Overrides   
        public override void OnApplyTemplate()
		{
			base.OnApplyTemplate();

            this.Loaded += new RoutedEventHandler(this.RadialControl_Loaded);


            Dial = ResourceRoot.FindName("PART_Dial") as FrameworkElement;
			
                //WTF 
               
              //  this.GradientDarkColor = Color.FromArgb(0xFF, 0xFF, 0x00, 0x00);
              //  this.GradientLightColor = Color.FromArgb(0xFF, 0xFF, 0x00, 0x00);
                SetDialGradient();

                Dial.RenderTransformOrigin = new Point(.5d, .5d);
				Dial.MouseLeftButtonDown += new MouseButtonEventHandler(Dial_MouseLeftButtonDown);   

			SnapTheta = DesiredTheta = Angle;
            CoerceTheta(this, Angle);
            if (Dial != null)
                Dial.RenderTransform = new RotateTransform(Angle, 0.0d, 0.0d);
        }        
        protected override void OnMouseLeftButtonUp(System.Windows.Input.MouseButtonEventArgs e)
           {
               base.OnMouseLeftButtonUp(e);
               if (IsEnabled && IsMouseCaptured)
               {
                   BeginAnimation(SnapTheta, RotationState.MouseUpAnimatingToSnapAngle);

                   ReleaseMouseCapture();
               }
           }
        protected override void OnMouseMove(System.Windows.Input.MouseEventArgs e)
           {
               base.OnMouseMove(e);
               if (IsEnabled && IsMouseCaptured)
               {
                   double theta = SnapTheta;

                   Point pt = e.GetPosition(this);
                   double deltaX = pt.X - ActualWidth / 2;
                   double deltaY = ActualHeight / 2 - pt.Y;

                   double pointerTheta = CalculatePointerThetaInDegrees(e.GetPosition(this));

                   // figure out where the closest attractor to the pointer is.
                   PointerSnapTheta = pointerTheta - pointerTheta % SnapAngle;
                   double pointerSnapThetaDelta = pointerTheta % SnapAngle;
                   if (pointerTheta % SnapAngle > SnapAngle / 2)
                   {
                       PointerSnapTheta = PointerSnapTheta + SnapAngle == 360 ? 0 : PointerSnapTheta + SnapAngle;
                       pointerSnapThetaDelta = pointerSnapThetaDelta - SnapAngle;
                   }

                   if (!SnapToSnapAngle)
                       PointerSnapTheta = pointerTheta;

                   double theta2PointerDelta = Trig.CalculateDelta(PointerSnapTheta, SnapTheta);

                   double theta2PointerDelta2 = theta2PointerDelta - PreviousThetaDelta;

                   SnapTheta = AddDeltaToTheta(SnapTheta, theta2PointerDelta2);

                   // If pointer is within the field of influence of the nearest snap...
                   if (SnapToSnapAngle && Math.Abs(pointerSnapThetaDelta) < SnapAngle / 3)
                   {
                       if (State == RotationState.SnappedToPointer || State == RotationState.AnimatingToPointer)
                       {
                           BeginAnimation(theta, RotationState.AnimatingToSnapAngle);
                       }
                       else if (CursorAttractsNearestSnap && State != RotationState.AnimatingToSnapAngle)
                       {
                           DesiredTheta = theta + Math.Log10(Math.Abs(pointerSnapThetaDelta) + 1) * Math.Sign(pointerSnapThetaDelta);
                       }
                   }
                   else
                   {
                       theta = AddDeltaToTheta(theta, theta2PointerDelta2 + (CursorAttractsNearestSnap && SnapToSnapAngle ? pointerSnapThetaDelta : 0));

                       if (State == RotationState.SnappedToSnapAngle || State == RotationState.AnimatingToSnapAngle)
                       {
                           BeginAnimation(theta, RotationState.AnimatingToPointer);
                       }
                       else
                       {
                           DesiredTheta = theta;
                       }
                   }

                   if (DesiredTheta > theta && Direction != RotationDirection.Clockwise)
                   {
                       Direction = RotationDirection.Clockwise;
                   }

                   if (DesiredTheta < theta && Direction != RotationDirection.CounterClockwise)
                   {
                       Direction = RotationDirection.CounterClockwise;
                   }

                   PreviousThetaDelta = Trig.CalculateDelta(PointerSnapTheta, SnapTheta);
               }
           }
        #endregion

        #region Private Methods
        private void SetDialGradient()
        {
            this.RadialGradientBrush = new RadialGradientBrush();
            this.RadialGradientBrush.GradientStops = new GradientStopCollection() { new GradientStop(this.GradientLightColor, 0), new GradientStop(this.GradientDarkColor, 1) };

            Ellipse o = GetTemplateChild("DIAL") as Ellipse;
            o.Fill = RadialGradientBrush;
        }    
        private void GradientColorChanged()
        {
            if (this.GradientLightBrush == null || (this.GradientLightBrush != null && this.GradientLightBrush.Color != this.GradientLightColor))
            {
                this.GradientLightBrush = new SolidColorBrush(this.GradientLightColor);
                Freeze(this.GradientLightBrush);
            }
            if (this.GradientDarkBrush == null || (this.GradientDarkBrush != null && this.GradientDarkBrush.Color != this.GradientDarkColor))
            {
                this.GradientDarkBrush = new SolidColorBrush(this.GradientDarkColor);
                Freeze(this.GradientDarkBrush);
            }
        }
        private void Freeze(Brush brush)
        {

            brush.Freeze();

        }
        private void DeterminePositiveOrNegativeChange(double oldValue, double newValue)
        { 
        
        }
        private double AddDeltaToTheta(double theta, double delta)
           {
               double result = theta + delta;
               result %= 360;
               while (result < 0) result += 360;

               if (RotationIsConstrained)
               {
                   double cclock = CounterClockwiseMostAngle;
                   cclock %= 360;
                   while (cclock < 0) cclock += 360;
                   double clock = ClockwiseMostAngle;
                   clock %= 360;
                   while (clock < 0) clock += 360;

                   if (((Trig.CalculateDelta(cclock, clock) < 0) && (Trig.CalculateDelta(result, cclock) < 0 || Trig.CalculateDelta(result, clock) > 0))
                       || ((Trig.CalculateDelta(cclock, clock) > 0) && (Trig.CalculateDelta(result, cclock) < 0 && Trig.CalculateDelta(result, clock) > 0)))
                   {
                       if (Math.Abs(Trig.CalculateDelta(result, cclock)) < Math.Abs(Trig.CalculateDelta(result, clock)))
                           result = cclock;
                       else
                           result = clock;
                   }
               }

               return result;
           }
        private void BeginAnimation(double desiredTheta, RotationState rotationState)
           {
               DesiredTheta = desiredTheta;
               State = rotationState;
               PrevTime = TimeSpan.Zero;
               AngularVelocity = 0.1f;
               stopwatch = new Stopwatch();
               this.stopwatch.Start();
               isAnimating = true;
           }
        private void StopAnimation(double desiredTheta, RotationState rotationState)
           {
               DesiredTheta = desiredTheta;
               State = rotationState;
               stopwatch = null;
               isAnimating = false;
           }
        private double CalculatePointerThetaInDegrees(Point pt)
           {
               double deltaX = pt.X - ActualWidth / 2;
               double deltaY = ActualHeight / 2 - pt.Y;

               if (Math.Abs(deltaX) < offlimits) deltaX = offlimits * Math.Sign(deltaX) == 0 ? 1 : Math.Sign(deltaX);
               if (Math.Abs(deltaY) < offlimits) deltaY = offlimits * Math.Sign(deltaY) == 0 ? 1 : Math.Sign(deltaY);

               double theta = Math.Atan(deltaX / deltaY);
               if (deltaY < 0)
                   theta += Math.PI;
               double thetaDegrees = theta * 180 / Math.PI;
               if (thetaDegrees < 0) thetaDegrees += 360;
               return thetaDegrees;
           }     
        private string SnapValueFromSnapAngle(double newTheta)
           {
               if (Math.Abs(Trig.CalculateDelta(newTheta, SnapTheta)) < SnapToValueTolerance)
               {
                   double test = CounterClockwiseMostAngle;
                   foreach (string s in SnapValues)
                   {
                       if (Math.Abs(Trig.CalculateDelta(test, SnapTheta)) < SnapToValueTolerance)
                           return s;
                       test = AddDeltaToTheta(test, SnapAngle);
                   }
               }
               return string.Empty;
           }
        #endregion Private Methods

        #region Event Handlers
        private void Dial_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
               if (IsEnabled)
               {
                   // if an animation is underway then stop it.
                   Angle = DesiredTheta;
                   CompositionTarget.Rendering -= this.CompositionTarget_OnRendering;
                   State = RotationState.SnappedToSnapAngle;
                   this.stopwatch = null;

                   SnapTheta = Angle;

                   double dragTheta = CalculatePointerThetaInDegrees(e.GetPosition(this));

                   // figure out where the closest attractor is.
                   double snapTheta = dragTheta - dragTheta % SnapAngle;
                   if (dragTheta % SnapAngle > SnapAngle / 2)
                   {
                       snapTheta = snapTheta + SnapAngle == 360 ? 0 : snapTheta + SnapAngle;
                   }

                   if (!SnapToSnapAngle)
                       snapTheta = dragTheta;

                   PreviousThetaDelta = Trig.CalculateDelta(snapTheta, SnapTheta);

                   CaptureMouse();

                   CompositionTarget.Rendering += this.CompositionTarget_OnRendering;
               }
           }
        private void CompositionTarget_OnRendering(object sender, EventArgs e)
           {
               if (RotationIsAnimated && SnapToSnapAngle && (State == RotationState.AnimatingToSnapAngle || State == RotationState.AnimatingToPointer || State == RotationState.MouseUpAnimatingToSnapAngle))
               {
                   double deltaBetween = Trig.CalculateDelta(Angle, DesiredTheta);
                   double signBefore = 0;
                   if (deltaBetween != 0)
                   {
                       signBefore = deltaBetween / Math.Abs(deltaBetween);
                   }

                   TimeSpan currentTime = this.stopwatch.Elapsed;
                   double elapsedTime = (currentTime - this.PrevTime).TotalSeconds;
                   if (elapsedTime < 0) return;
                   this.PrevTime = currentTime;

                   double force = (-SnapSpringStrength * deltaBetween) - (SnapSpringDampening * AngularVelocity);
                   AngularVelocity += force * elapsedTime;
                   Angle += AngularVelocity * elapsedTime + .5f * force * elapsedTime * elapsedTime;

                   if (Math.Abs(AngularVelocity) < 0.01f)
                   {
                       Angle = DesiredTheta;
                       if (State == RotationState.MouseUpAnimatingToSnapAngle)
                       {
                           CompositionTarget.Rendering -= this.CompositionTarget_OnRendering;
                       }
                       State = RotationState.SnappedToSnapAngle;
                       this.stopwatch = null;
                   }
               }
               else
               {
                   Angle = DesiredTheta;
               }
           }
        private void RadialControl_Loaded(object sender, RoutedEventArgs e)
        {
            this.isAnimating = false;
            this.isLoaded = true;
            this.RadialGradientBrush = new RadialGradientBrush();
        }
        #endregion

        #region INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }

        #endregion

    }
}

